#include <iostream>
#include <string>
#include <boost/timer.hpp>
#include <boost/thread/thread.hpp>
#include <boost/program_options.hpp>

#include "protonos8.hpp"

static void
setup_locations(protonos8::client& m, int iloc, int oloc)
{
    uint32_t iaddr = (iloc < 4) ? 0x60000020 + iloc*4 : 0x60000060 + (iloc-4)*4;
    uint32_t oaddr = (oloc < 4) ? 0x60000020 + oloc*4 : 0x60000060 + (oloc-4)*4;
	uint32_t direction;

    std::cout << " Setup location " << iloc << " as input [0x" << std::hex << iaddr << " <- 0x000].\n";
	direction = 0x000; m.raw_write(0, iaddr, direction);
    std::cout << " Setup location " << oloc << " as output [0x" << std::hex << oaddr << std::dec << " <- 0xfff].\n";
    direction = 0xfff; m.raw_write(0, oaddr, direction);
}

static bool
do_running_test(protonos8::client& m, int iloc, int oloc, bool running_zero, uint32_t mask, std::size_t delay)
{
    bool err = false;
    uint32_t iaddr = (iloc < 4) ? 0x60000000 + iloc*4 : 0x60000040 + (iloc-4)*4;
    uint32_t oaddr = (oloc < 4) ? 0x60000000 + oloc*4 : 0x60000040 + (oloc-4)*4;

    std::cout << "  running " << ((running_zero) ? "0" : "1") << " ..." << std::flush;

    for (int i = 0; i < 12; i++) {
        uint32_t pattern;
        uint32_t data;

		data = 0;
        pattern = (running_zero) ? ~(1<<i) & 0xfff : (1 << i);

		pattern &= mask;
        m.raw_write(0, oaddr, pattern);

		if (delay)
			boost::this_thread::sleep(boost::posix_time::milliseconds(delay)); 

        m.raw_read(0, iaddr, data);
        data &= mask;
        if (data != pattern) {
			std::string str;
            std::cout << "\n  ERR: output[0x" << std::hex << oaddr << "] = " <<
                pattern << " input[0x" << iaddr << "] = " << data <<
                std::dec << std::flush;
			std::getline(std::cin, str);
            err = true;
        }
    }

    return err;
}

static bool
test_locations(protonos8::client& m211, int iloc, int oloc, uint32_t mask, std::size_t delay)
{
	bool err = false;

	std::cout << "Testing locations " << iloc << "-" << oloc << std::endl;

	setup_locations(m211, iloc, oloc);
	if (do_running_test(m211, iloc, oloc, false, mask, delay)) err = true;
	if (do_running_test(m211, iloc, oloc, true, mask, delay)) err = true;

	return err;
}

static void
test_throughput(protonos8::client& m211, int iloc, int oloc, uint32_t mask, std::size_t delay)
{
	setup_locations(m211, iloc, oloc);

    uint32_t iaddr = (iloc < 4) ? 0x60000000 + iloc*4 : 0x60000040 + (iloc-4)*4;
    uint32_t oaddr = (oloc < 4) ? 0x60000000 + oloc*4 : 0x60000040 + (oloc-4)*4;

	std::cout << "Measure throughput locations " << iloc << "-" << oloc << std::endl;
	
	const double t0 = 10.0; // loop for 10 seconds
	{
		uint32_t pattern = 0;
		std::size_t total = 0;
		std::size_t lost = 0;
		boost::timer t;

		t.restart();
		for (; t.elapsed() < t0; total++) {
				if (m211.raw_write(0, oaddr, pattern) != protonos8::success)
						lost++;
		}
		std::cout << total/t0 << " raw_write xfers / second; " << lost << " xfers lost\n";

		t.restart();

		total = 0;
		lost = 0;
		for (; t.elapsed() < t0; total++) {
				if (m211.raw_read(0, iaddr, pattern) != protonos8::success)
						lost++;
		}
		std::cout << total/t0 << " raw_read xfers / second; " << lost << " xfers lost\n";

	}
}

int main(int argc, char *argv[])
{
	try
    {
		namespace po = boost::program_options;
		po::options_description desc("Allowed options");
		desc.add_options()
			("help", "produce help message")
			("host", po::value<std::string>()->default_value("localhost"), "server name")
			("port", po::value<std::string>()->default_value("1111"), "socket port")
			("input", po::value<int>()->default_value(0), "input piggyback location")
			("output", po::value<int>()->default_value(1), "output piggyback location")
			("mask", po::value<std::string>()->default_value("0xfff"), "bit masking")
			("delay", po::value<int>()->default_value(0), "spread delay (milliseconds)")
			("pass", po::value<int>()->default_value(1), "total pass counter")
			("measure", "measure throughput")
		;
		po::variables_map vm;
		po::store(po::parse_command_line(argc, argv, desc), vm);
		po::notify(vm);    

		if (vm.count("help")) {
			std::cout << desc << "\n";
			return 1;
		}
		
		std::cout << "M211@" <<
			vm["host"].as<std::string>() << ":" <<
			vm["port"].as<std::string>() << std::endl;

		protonos8::client m211(vm["host"].as<std::string>().c_str(), vm["port"].as<std::string>().c_str());

		uint32_t mask = strtoul(vm["mask"].as<std::string>().c_str(), NULL, 16);
		std::size_t delay = vm["delay"].as<int>();

		if (vm.count("measure")) {
			test_throughput(m211, vm["input"].as<int>(), vm["output"].as<int>(), mask, delay);
			return 1;
		}

		std::size_t pass = vm["pass"].as<int>();
		std::size_t total = 1;
		bool err;
        do {
			
			std::cout << "#" << total++ << "\n";

			err = false;

			err = test_locations(m211, vm["input"].as<int>(), vm["output"].as<int>(), mask, delay);

			if (!err)
				std::cout << "\nOK.\n";
			else 
				std::cout << "\nERR.\n";

        } while (!(pass && total > pass));

		std::cout << std::endl;

	} catch (std::exception& e) {
		std::cerr << "Exception: " << e.what() << "\n";
	}

	return 0;
}
